using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Think.Helpers;

namespace Think.Problems
{
    public class Tableau
    {
        private readonly IRule _rule;

        private readonly List<string> _faceDown = new List<string>();
        private readonly List<string> _faceUp = new List<string>();

        /// <summary>
        /// Constructor.
        /// </summary>
        public Tableau()
        {
            _rule = new DefaultRule();
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_rule">Tableau rule.</param>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_rule"/>" is null.</exception>
        public Tableau(IRule a_rule)
        {
            #region Argument Validation

            if (a_rule == null)
                throw new ArgumentNullException("a_rule");

            #endregion

            _rule = a_rule;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_other">Other tableau.</param>
        protected Tableau(Tableau a_other)
        {
            _rule = a_other._rule;
            _faceDown = new List<string>(a_other._faceDown);
            _faceUp = new List<string>(a_other._faceUp);
        }

        /// <summary>
        /// Total number of cards in this tableau.
        /// </summary>
        public int Count
        {
            get { return FaceDownCount + FaceUpCount; }
        }

        /// <summary>
        /// Total number of cards that are face down.
        /// </summary>
        public int FaceDownCount 
        {
            get { return _faceDown.Count; } 
        }

        /// <summary>
        /// Total number of cards that are face up.
        /// </summary>
        public int FaceUpCount
        {
            get { return _faceUp.Count; }
        }

        /// <summary>
        /// Top most face up card in this tableau.
        /// </summary>
        public string TopCard
        {
            get { return _faceUp.LastOrDefault(); }
        }

        /// <summary>
        /// Bottom must face up card in this tableau. Tableau must not have any face down cards.
        /// </summary>
        public string BottomCard
        {
            get
            {
                if (_faceDown.Any())
                    return null;

                return _faceUp.FirstOrDefault();
            }
        }

        /// <summary>
        /// Whether the top most face down card can be flipped.
        /// </summary>
        public bool CanFlip
        {
            get { return _faceDown.Any() && !_faceUp.Any(); }
        }

        /// <summary>
        /// Face up cards.
        /// </summary>
        public String[] FaceUp
        {
            get { return _faceUp.ToArray(); }
        }


        /// <summary>
        /// Add the given card (<paramref name="a_card"/>) to the tableau.
        /// </summary>
        /// <param name="a_card">Card to add.</param>
        /// <param name="a_side">Card side.</param>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_card"/>" is null.</exception>
        public void Add(string a_card, CardSide a_side)
        {
            #region Argument Validation

            if (a_card == null)
                throw new ArgumentNullException("a_card");

            #endregion

            if (a_side == CardSide.FaceDown)
                _faceDown.Add(a_card);

            if (a_side == CardSide.FaceUp)
                _faceUp.Add(a_card);
        }


        /// <summary>
        /// Add the card from the given deck (<paramref name="a_deck"/>) to the tableau. 
        /// </summary>
        /// <param name="a_deck">Deck.</param>
        /// <param name="a_side">Card side.</param>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_deck"/>" is null.</exception>
        public void Add(IDeck a_deck, CardSide a_side)
        {
            #region Argument Validation

            if (a_deck == null)
                throw new ArgumentNullException("a_deck");

            #endregion

            var card = a_deck.Draw();

            Add(card, a_side);
        }

        /// <summary>
        /// Add a card from the given deck (<paramref name="a_deck"/>) to the tableau if the given limit (<paramref name="a_limit"/>) is not met.
        /// </summary>
        /// <param name="a_deck">Deck.</param>
        /// <param name="a_limit">Card limit.</param>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_deck"/>" is null.</exception>
        public void Add(IDeck a_deck, int a_limit)
        {
            #region Argument Validation

            if (a_deck == null)
                throw new ArgumentNullException("a_deck");

            #endregion

            if (Count < a_limit - 1)
                Add(a_deck.Draw(), CardSide.FaceDown);
            else if (Count == a_limit - 1)
                Add(a_deck.Draw(), CardSide.FaceUp);
        }

        /// <summary>
        /// Flip the top most face down card if it can be flipped.
        /// </summary>
        public void Flip()
        {
            if (!CanFlip)
                return;

            var flippedCard = _faceDown.PopLast();
            _faceUp.PushFirst(flippedCard);
        }

        /// <summary>
        /// Split this tableau at the given index (<paramref name="a_index"/>).
        /// </summary>
        /// <param name="a_index">Given index.</param>
        /// <returns>Tableau.</returns>
        public Tableau Split(int a_index)
        {
            var tableau = new Tableau();
            tableau._faceUp.AddRange(_faceUp.Split(a_index));

            return tableau;
        }

        /// <summary>
        /// Determine whether the given tableau (<paramref name="a_travelling"/>) can be set on top of this one.
        /// </summary>
        /// <param name="a_travelling">Travelling tableau.</param>
        /// <returns>True if tabluea can be set.</returns>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_travelling"/>" is null.</exception>
        public bool CanSet(Tableau a_travelling)
        {
            #region Argument Validation

            if (a_travelling == null)
                throw new ArgumentNullException("a_travelling");

            #endregion

            if (a_travelling.FaceDownCount > 0)
                return false;

            if (Count <= 0)
                return _rule.CanSetOnEmpty(a_travelling.BottomCard);

            if (FaceUpCount <= 0)
                return false;

            return _rule.CanSet(this.TopCard, a_travelling.BottomCard);
        }

        /// <summary>
        /// Determine whether the given card (<paramref name="a_card"/>) can be set on top of this tableau.
        /// </summary>
        /// <param name="a_card">Given card.</param>
        /// <returns>True if card can be set.</returns>
        /// <exception cref="ArgumentNullException">Thrown if "<paramref name="a_travelling"/>" is null.</exception>
        public bool CanSet(string a_card)
        {
            #region Argument Validation

            if (a_card == null)
                throw new ArgumentNullException("a_card");

            #endregion

            if (Count <= 0)
                return _rule.CanSetOnEmpty(a_card);

            if (FaceUpCount <= 0)
                return false;

            return _rule.CanSet(this.TopCard, a_card);
        }

        /// <summary>
        /// Set the given tableau (<paramref name="a_travelling"/>) on this tableau.
        /// </summary>
        /// <param name="a_travelling">Travelling tableau.</param>
        public void Set(Tableau a_travelling)
        {
            if (!CanSet(a_travelling))
                throw new InvalidOperationException();

            _faceUp.AddRange(a_travelling._faceUp);
        }

        /// <summary>
        /// Clone this tableau.
        /// </summary>
        /// <returns>Clone of this tableau.</returns>
        public Tableau Clone()
        {
            return new Tableau(this);
        }
        
        class DefaultRule : IRule
        {
            /// <summary>
            /// Whether the given travelling tableau (<paramref name="a_base"/>) can be set upon the given base 
            ///     talbeau (<paramref name="a_travelling"/>).
            /// </summary>
            /// <param name="a_base">Base card.</param>
            /// <param name="a_travelling">Travelling card.</param>
            /// <returns>true if travelling tablea can be set.</returns>
            public bool CanSet(string a_base, string a_travelling)
            {
                return true;
            }

            /// <summary>
            /// Whether the given travelling card (<paramref name="a_travelling"/>) can be set on an empty tableau.
            /// </summary>
            /// <param name="a_travelling">Travelling card.</param>
            /// <returns>True if travelling card can be set.</returns>
            public bool CanSetOnEmpty(string a_travelling)
            {
                return true;
            }
        }

    }
}